/*******************************************************************************

    copyright:  Copyright (c) 2007 Matthias Walter. All rights reserved

    license:    BSD style: $(LICENSE)

    version:    Initial release: May 2007

    author:     Matthias Walter

*******************************************************************************/

module fcgi.Protocol;

import mango.io.protocol.Reader;
import mango.io.protocol.Writer;

const byte FastCGIVersion = 1;

// Roles

enum FastCGIRole : byte
{
    UnknownRole = 0,
    Responder = 1,
    Authorizer = 2,
    Filter = 3
};

// Flags

enum FastCGIFlags : byte
{
    NoFlag = 0,
    KeepAlive = 1
};

// Streams

enum FastCGIStreamType : byte
{
    NoStream = 0,
    Stdin = 5,
    Stdout = 6,
    Stderr = 7,
    Data = 8
};

// Record types

enum FastCGIRecordType : ubyte
{
     BeginRequest = 1,
     AbortRequest = 2,
     EndRequest = 3,
     Params = 4,
     Stdin = 5,
     Stdout = 6,
     Stderr = 7,
     Data = 8,
     GetValues = 9,
     GetValuesResult = 10,
     UnknownRecordType = 11
};

// Protocol status

enum FastCGIProtocolStatus : byte
{
     RequestComplete = 0,
     CannotMultiplexConnection = 1,
     Overloaded = 2,
     UnknownRole = 3
}


// Header structure

class FastCGIHeader : IReadable
{
    private byte version_;
    private FastCGIRecordType recordType_;
    private short requestID_;
    private short contentLength_;
    private byte paddingLength_;

    // ctor for reading

    this ()
    {
        recordType_ = FastCGIRecordType.UnknownRecordType;
    }

    // ctor for writing

    this (FastCGIRecordType recordType, int requestID, int contentLength)
    {
        version_ = FastCGIVersion;
        recordType_ = recordType;
        requestID_ = requestID;
        contentLength_ = contentLength;
        paddingLength_ = 0;
    }

    void read (IReader input)
    {
        ubyte a,b;

        // version
        input (version_);
        // recordType
        input (cast(ubyte) recordType_);
        // requestID
        input (a) (b);
        requestID_ = a << 8 | b;
        // contentLength
        input (a) (b);
        contentLength_ = a << 8 | b;
        // paddingLength
        input (paddingLength_);
        // reserved
        input (a);
    }

    void write(IWriter output)
    {
        ubyte a,b;

        // version
        output(version_);
        // recordType
        output (cast(byte) recordType_);
        // requestID
        a = (requestID_) & 8;
        b = (requestID_ >> 8) & 8;
        output (a) (b);
        // contentLength
        a = (contentLength_) & 8;
        b = (contentLength_ >> 8) & 8;
        output (a) (b);
        // paddingLength
        output (paddingLength_);
        // reserved
        a = 0;
        output (a);
    }

    void readPadding (IReader input)
    {
      //input.buffer.skip(paddingLength_); 
      if (paddingLength_ > 0)
      {
         byte[] data = new byte[paddingLength_];
         input (data);
      }
    }

    bool checkVersion ()
    {
        return version_ == FastCGIVersion;
    }

    bool isInputStream ()
    {
        return (recordType_ == FastCGIRecordType.Stdin || recordType_ == FastCGIRecordType.Data);
    }

    FastCGIStreamType streamType()
    {
        if (recordType_ == FastCGIRecordType.Stdin)
            return FastCGIStreamType.Stdin;
        if (recordType_ == FastCGIRecordType.Data)
            return FastCGIStreamType.Data;
        if (recordType_ == FastCGIRecordType.Stdout)
            return FastCGIStreamType.Stdout;
        if (recordType_ == FastCGIRecordType.Stderr)
            return FastCGIStreamType.Stderr;
        else
            return FastCGIStreamType.NoStream;
    }

    FastCGIRecordType recordType ()
    {
        return recordType_;
    }

    int requestID ()
    {
        return requestID_;
    }

    int contentLength ()
    {
        return contentLength_;
    }

    int paddingLength ()
    {
        return paddingLength_;
    }
}

class FastCGIBeginRequest : IReadable
{
    private FastCGIRole role_;
    private FastCGIFlags flags_;

    this ()
    {
       role_ = FastCGIRole.UnknownRole;
       flags_ = FastCGIFlags.NoFlag;
    }

    void read (IReader input)
    {
        byte a,b;

        input (a) (b);
        role_ = cast(FastCGIRole) ((a << 8) | b);

        input (cast(byte) flags_);

        //input.buffer.skip(5);
        byte[] data = new byte[5];
        input (data);
    }

    int length ()
    {
        return 8;
    }

    FastCGIRole role ()
    {
        return role_;
    }

    bool isKeepAlive ()
    {
        return (flags_ & FastCGIFlags.KeepAlive) != 0;
    }
}

int readVariableLength (IReader input)
{
    ubyte a;

    input (a);

    if ((a & 0x80) == 0)
        return a;

    ubyte b,c,d;

    input (b) (c) (d);

    return ((a & 0x7f) << 24) | (b << 16) | (c << 8) | (d);
}

void writeVariableLength (inout char[] buffer, char[] text)
{
    if (text.length < 128)
    {
        buffer.length = text.length+1;
        buffer[0] = text.length;
        buffer[1 .. $] = buffer;
    }
    else
    {
        int len = text.length;
        buffer.length = len+4;
        buffer[0] = 0xff & ((len >> 24) | 0x80);
        buffer[1] = 0xff & (len >> 16);
        buffer[2] = 0xff & (len >> 8);
        buffer[3] = 0xff & (len);
        buffer[4 .. $] = text;
    }
}


// bla

struct FastCGIHeaderStruct
{
     byte protocolVersion;
     FastCGIRecordType recordType;
     byte reqID1;
     byte reqID2;
     byte contentLength1;
     byte contentLength2;
     byte paddingLength;
     byte reserved;
}

// BeginRequest structure

struct FastCGIBeginRequestStruct
{
     byte role1;
     byte role2;
     byte flags;
     byte[5] reserved;
} 

// EndRequest structure

struct FastCGIEndRequest
{
     byte appStatus1;
     byte appStatus2;
     byte appStatus3;
     byte appStatus4;
     byte protocolStatus;
     byte[3] reserved;
}

// Process info

struct FastCGIRecordInfo
{
     FastCGIStreamType streamType;
     FastCGIRecordType recordType;
     int contentLength;
     int paddingLength;
}

